import numpy as np
import csv
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
sns.set(color_codes = True)
from data import *
from scipy import stats
from sklearn.decomposition import PCA
On se donne ici un exemple simple de données qui contiennent $N = 6$ individus et $p = 2$ variables : $X_1$ et $X_2$.
On va étudier dans un premier temps l'effet du centrage et de la réduction des données : on verra graphiquement que cela correspond à un changement de repère du nuage de points.
X1 = [3, 6, 4, 2, -1, 1]
X2 = [2, 4, 3, 3, 1, 1]
X = np.zeros((6, 2))
X[:,0] = X1.copy()
X[:,1] = X2.copy()
print('Données brutes : \n', X)
plt.figure(figsize = (9,7))
plt.plot(X1, X2 , 's')
plt.axis([-2,7, -2, 5])
plt.axvline(linewidth=2, color='blue')
plt.axhline(linewidth=2, color='blue')
#plt.legend(['données brutes'], fontsize = 14)
plt.title('Données brutes', fontsize = 20)
plt.xticks(fontsize = 18)
plt.yticks(fontsize = 18)
plt.xlabel('X1',fontsize = 18)
plt.ylabel('X2',fontsize = 18)
plt.show()
# Moyenne
m1 = np.mean(X1)
m2 = np.mean(X2)
print('Moyenne de X1 : ' + str(m1) + ', et moyenne de X2 :' + str(m2))
# Ecart-type
sig1 = np.std(X1)
sig2 = np.std(X2)
print('Ecart-type de X1 : ' + str(sig1) + ', et écart-type de X2 :' + str(sig2))
plt.figure(figsize = (9,7))
plt.plot(X1, X2 , 's')
plt.axvline(linewidth=2, color='blue')
plt.axhline(linewidth=2, color='blue')
plt.axis([-2,7, -2, 5])
plt.axvline(x = m1, linewidth=2, color='r')
plt.axhline(y = m2, linewidth=2, color='r')
plt.plot(m1, m2 , 'or')
plt.xticks(fontsize = 18)
plt.yticks(fontsize = 18)
plt.xlabel('X1',fontsize = 18)
plt.ylabel('X2',fontsize = 18)
plt.title('Représentation du centre du nuage de points' , fontsize = 20)
plt.show()
L'opération de centrage consiste à positionner l'origine du repère sur le centre de gravité du nuage de points.
plt.figure(figsize = (9,7))
plt.plot(X1-m1, X2-m2 , 'o')
plt.axvline(linewidth=2, color='r')
plt.axhline(linewidth=2, color='r')
plt.axis([-4,5, -2, 5])
plt.title('Centrage du nuage de points', fontsize = 20)
plt.legend(['données brutes centrées'], fontsize = 18)
plt.xticks(fontsize = 18)
plt.yticks(fontsize = 18)
plt.xlabel('X1 - $m_1$',fontsize = 18)
plt.ylabel('X2 - $m_2$',fontsize = 18)
plt.show()
Xtilde = np.zeros((6, 2))
Xtilde[:,0] = (X1.copy() - m1)/sig1
Xtilde[:,1] = (X2.copy() - m2)/sig2
print('Données centrées et réduites : \n', Xtilde)
plt.figure(figsize = (9,7))
plt.plot(X1-m1, X2-m2 , 'o')
plt.plot(Xtilde[:,0], Xtilde[:,1], '*r' , markersize = 20 )
plt.axvline( linewidth=2, color='r')
plt.axhline( linewidth=2, color='r')
plt.axis([-4,5, -2, 5])
plt.title('Effet de la réduction sur le nuage de points centré', fontsize = 20)
plt.legend(['données brutes centrées', 'données centrées réduites'], fontsize = 18)
plt.xticks(fontsize = 18)
plt.yticks(fontsize = 18)
plt.xlabel('X1 - $m_1$',fontsize = 18)
plt.ylabel('X2 - $m_2$',fontsize = 18)
plt.show()
C = np.dot(Xtilde.T, Xtilde)/6.
C
On constate que la variance de la variable X1 est beaucoup plus grande que la variance de X2, ce qui se retrouve graphiquement : la dispersion est plus forte selon l'axe x1 que selon l'axe x2.
Rappel : le but de l'ACP est de trouver un nouvel espace de représentation (engendré par les composantes principales ) qui représentent le mieux le nuage de points ( = les observations) au sens de la quantité d'information portée par chaque axe. La première composante principale doit porter la plus grande part d'information du nuage de points, la seconde composante principale doit porter la plus grande part d'information non expliquée par la première composante principale, ainsi de suite jusqu'au calcul de la $p^{\mbox{ème}}$ composante principale.
Note : quand on parle d'information portée par un axe, on parle en réalite de la dispersion des données suivant cet axe (i.e. la variance de la variable associée à cet axe)
L'ACP ressemble sur le principe à un changement de repère du nuage de points, mais elle comporte en plus l'objectif de réduire la dimension du problème en trouvant un espace où l'information contenue dans le nuage de point peut être contenue sur 2 ou 3 composantes dans l'idéal en déformant le moins possible l'information contenu dans le nuage de points initial.
En cours nous avons vu seulement des exemples très simples, mais l'ACP peut fournir des résultats plus difficilement interprétables (il est parfois (souvent) compliqué de donner un sens physique aux nouvelles variables dans le nouvel espace de représentation, en particulier lorsque le nombre $p$ de variables est grand).
Attention : dans le poly, on a vu l'ACP sur les données centrées et réduites ici on applique l'ACP sur les données centrées non réduites.
# Centrage des données X
Xc = np.zeros((6, 2))
Xc[:,0] = (X1.copy() - m1)
Xc[:,1] = (X2.copy() - m2)
acp = PCA(2) # Initialisation de l'objet PCA (qui va calculer les différentes étapes de l'ACP)
acp.fit(Xc) # Calcul des différentes étapes de l'ACP de Xtilde
acp.explained_variance_ # Valeurs propres ordonnées (décroissant)
acp.explained_variance_ratio_
Les composantes principales sont les deux nouvelles variables U1 et U2 portées par les axes u1 et u2 ci-dessous. La variance portée par la première composante principale u1 représente 94% de l'information totale contenue dans les variables. Ce qui veut dire qu'à elle seule, la variable U1 décrit relativement bien les données.
u1 = acp.components_[0]
print(u1)
u2 = acp.components_[1]
print(u2)
plt.figure(figsize = (9,7))
plt.plot(Xc[:,0], Xc[:,1], '*r' , markersize = 20)
plt.axvline( linewidth=2, color='r')
plt.axhline( linewidth=2, color='r')
plt.plot([-2*acp.components_[0][0] ,3*acp.components_[0][0] ],[-2*acp.components_[0][1],3*acp.components_[0][1] ], 'gray') # Premier vecteur propre
plt.plot([-acp.components_[1][0],acp.components_[1][0] ],[-acp.components_[1][1],acp.components_[1][1] ], 'gray') # Second vecteur propre
plt.plot([0,acp.components_[0][0] ],[0,acp.components_[0][1] ], linewidth=3) # Premier vecteur propre
plt.plot([0,acp.components_[1][0] ],[0,acp.components_[1][1] ],linewidth=3) # Second vecteur propre
plt.axis([-4, 4.5, -2, 2])
plt.axis('equal')
plt.xlabel('$\~X_1$', fontsize = 18)
plt.ylabel('$\~X_2$', fontsize = 18)
plt.xticks(fontsize = 18)
plt.yticks(fontsize = 18)
plt.title('Données centrées, u1 en bleu, u2 en vert', fontsize = 20)
plt.show()
Les deux composantes prinipales (i.e. les nouveaux axes du nouvel espace de représentation) en bleu et en vert sont tels que le premier axe (bleu) est dirigé dans la direction de la plus grande dispersion du nuage de points, et le second axe par construction est orthogonal au premier $\rightarrow$ ce qui s'interprête comme l'axe expliquant la part de variance qui n'a pas été expliquée par le premier axe.
Xproj = acp.transform(Xc)
print(Xproj)
plt.figure(figsize = (9,7))
plt.axvline( linewidth=2, color='gray')
plt.axhline( linewidth=2, color='gray')
plt.plot([0,1], [0,0],linewidth=3)
plt.plot([0,0], [0,1],linewidth=3)
plt.plot(Xproj[:,0], Xproj[:,1], '*r' , markersize = 20)
plt.axis([-4, 4.5, -2, 2])
plt.axis('equal')
plt.xlabel('$U_1$', fontsize = 18)
plt.ylabel('$U_2$', fontsize = 18)
plt.xticks(fontsize = 18)
plt.yticks(fontsize = 18)
plt.title('Données projetées sur u1 et u2', fontsize = 20)
plt.show()
np.var(Xproj[:,0])
np.var(Xproj[:,0])/(np.var(Xproj[:,0]) + np.var(Xproj[:,1]))
La part de variance portée par l'axe $U_1$ est de 96% ce qui signifie que l'axe $U_1$ porte à lui seul 96% de l'information contenue dans le nuage de points. Si l'on résume le nuage de point à une variable ($U_1$) on aura seulement 4% de perte = l'amplitude des points selon l'axe $U_2$.
np.var(Xproj[:,1])
np.var(Xproj[:,1])/(np.var(Xproj[:,0]) + np.var(Xproj[:,1]))
np.var(X[:,0])/(np.var(X[:,0]) + np.var(X[:,1]))
np.var(X[:,1])/(np.var(X[:,0]) + np.var(X[:,1]))
$\rightarrow$ on a bien maximiser la variance portée par le premier axe en changeant d'espace de représentation
Cette fois-ci on étudie un jeu de données contenant $N = 30$ individus représentés par 3 variables $X_1$, $X_2$ et $X_3$ avec $X_1$ et $X_2$ qui sont deux variables générées de manière indépendante (= a priori pas de relation entre ces deux variables) et $X_3$ est une combinaison linéaire de $X_1$ et $X_2$ + un bruit gaussien.
N = 30
X1 = np.random.randint(-10,10, N)
X2 = np.random.randint(-2,15, N)
X3 = 2*X1 -3*X2 + 4 + np.random.normal(0, 1.2, N) # Relation quasi linéaire entre X3 et les variables X1 et X2 (au bruit près)
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(X1,X2, X3)
ax.set_xlabel('X1')
ax.set_ylabel('X2')
ax.set_zlabel('X3')
plt.show()
# Calcul de la moyenne
m1 = np.mean(X1)
m2 = np.mean(X2)
m3 = np.mean(X3)
# Calcul de l'écart-type
sig1 = np.std(X1)
sig2 = np.std(X2)
sig3 = np.std(X3)
# Données centrées
Xc = np.zeros((N, 3))
Xc[:,0] = (X1 - m1)
Xc[:,1] = (X2 - m2)
Xc[:,2] = (X3 - m3)
C3D = np.dot(Xc.T, Xc)/N
print(C3D)
acp3 = PCA(3)
acp3.fit(Xc)
acp3.explained_variance_
np.cumsum(acp3.explained_variance_ratio_)
Xproj3D = acp3.transform(Xc)
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(Xproj3D[:,0],Xproj3D[:,1],Xproj3D[:,2])
ax.set_xlabel('U1')
ax.set_ylabel('U2')
ax.set_zlabel('U3')
plt.show()
Cor_Xproj3D = np.dot(Xproj3D.T, Xproj3D)/N
Cor_Xproj3D
plt.figure(figsize = (6,6))
plt.axvline( linewidth=2, color='gray')
plt.axhline( linewidth=2, color='gray')
plt.plot([0,1], [0,0],linewidth=3)
plt.plot([0,0], [0,1],linewidth=3)
plt.plot(Xproj3D[:,0], Xproj3D[:,1], 'o')
plt.xlabel('$U_1$', fontsize = 14)
plt.ylabel('$U_2$', fontsize = 14)
plt.axis('equal')
plt.show()
plt.figure(figsize = (6,6))
plt.axvline( linewidth=2, color='gray')
plt.axhline( linewidth=2, color='gray')
plt.plot([0,1], [0,0],linewidth=3)
plt.plot([0,0], [0,1],linewidth=3)
plt.plot(Xproj3D[:,1], Xproj3D[:,2], 'o')
plt.xlabel('$U_2$', fontsize = 14)
plt.ylabel('$U_3$', fontsize = 14)
plt.axis('equal')
plt.show()
plt.figure(figsize = (6, 6))
plt.axvline( linewidth=2, color='gray')
plt.axhline( linewidth=2, color='gray')
plt.plot([0,1], [0,0],linewidth=3)
plt.plot([0,0], [0,1],linewidth=3)
plt.plot(Xproj3D[:,0], Xproj3D[:,2], 'o')
plt.xlabel('$U_1$', fontsize = 14)
plt.ylabel('$U_3$', fontsize = 14)
plt.axis('equal')
plt.show()
On peut directement réduire la dimension en précisant à la fonction PCA de python que l'on souhaite conserver uniquement 2 composantes au lieu de 3. On vérifie ici que l'on obtient exactement les mêmes composantes $U_1$ et $U_2$ que précédemment.
acp2 = PCA(2)
acp2.fit(Xc)
acp2.explained_variance_
np.cumsum(acp2.explained_variance_ratio_)
Xproj2 = acp2.transform(Xc)
plt.figure(figsize = (6,6))
plt.axvline( linewidth=2, color='gray')
plt.axhline( linewidth=2, color='gray')
plt.plot([0,1], [0,0],linewidth=3)
plt.plot([0,0], [0,1],linewidth=3)
plt.plot(Xproj2[:,0], Xproj2[:,1], 'o')
plt.xlabel('$U_1$', fontsize = 14)
plt.ylabel('$U_2$', fontsize = 14)
plt.axis('equal')
plt.show()